/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.mail;

import java.io.IOException;
import java.net.*;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.mail.event.*;

import com.sun.mail.smtp.SMTPTransport;

/**
 * An abstract class that models a message transport.
 * Subclasses provide actual implementations.
 * <p>
 * 
 * Note that <code>Transport</code> extends the <code>Service</code> class,
 * which provides many common methods for naming transports, connecting to
 * transports, and listening to connection events.
 * 
 * @author John Mani
 * @author Max Spivak
 * @author Bill Shannon
 * 
 * @see javax.mail.Service
 * @see javax.mail.event.ConnectionEvent
 * @see javax.mail.event.TransportEvent
 */

public abstract class Transport extends Service {

	/**
	 * Constructor.
	 * 
	 * @param session
	 *            Session object for this Transport.
	 * @param urlname
	 *            URLName object to be used for this Transport
	 */
	public Transport(Session session, URLName urlname) {
		super(session, urlname);
	}

	/**
	 * Send a message. The message will be sent to all recipient
	 * addresses specified in the message (as returned from the
	 * <code>Message</code> method <code>getAllRecipients</code>),
	 * using message transports appropriate to each address. The
	 * <code>send</code> method calls the <code>saveChanges</code> method on the
	 * message before sending it.
	 * <p>
	 * 
	 * If any of the recipient addresses is detected to be invalid by the
	 * Transport during message submission, a SendFailedException is thrown.
	 * Clients can get more detail about the failure by examining the exception.
	 * Whether or not the message is still sent successfully to any valid
	 * addresses depends on the Transport implementation. See
	 * SendFailedException for more details. Note also that success does not
	 * imply that the message was delivered to the ultimate recipient, as
	 * failures may occur in later stages of delivery. Once a Transport accepts
	 * a message for delivery to a recipient, failures that occur later should
	 * be reported to the user via another mechanism, such as returning the
	 * undeliverable message.
	 * <p>
	 * 
	 * In typical usage, a SendFailedException reflects an error detected by the
	 * server. The details of the SendFailedException will usually contain the
	 * error message from the server (such as an SMTP error message). An address
	 * may be detected as invalid for a variety of reasons - the address may not
	 * exist, the address may have invalid syntax, the address may have exceeded
	 * its quota, etc.
	 * <p>
	 * 
	 * Note that <code>send</code> is a static method that creates and manages
	 * its own connection. Any connection associated with any Transport instance
	 * used to invoke this method is ignored and not used. This method should
	 * only be invoked using the form <code>Transport.send(msg);</code>, and
	 * should never be invoked using an instance variable.
	 * 
	 * @param msg
	 *            the message to send
	 * @exception SendFailedException
	 *                if the message could not
	 *                be sent to some or any of the recipients.
	 * @exception MessagingException
	 * @see Message#saveChanges
	 * @see Message#getAllRecipients
	 * @see #send(Message, Address[])
	 * @see javax.mail.SendFailedException
	 */
	public static void send(Message msg) throws MessagingException {
		msg.saveChanges(); // do this first
		send0(msg, msg.getAllRecipients(), null);
	}
	
	public static void send(Message msg, WriteListener listener) throws MessagingException {
		msg.saveChanges(); // do this first
		send0(msg, msg.getAllRecipients(), listener);
	}

	/**
	 * Send the message to the specified addresses, ignoring any
	 * recipients specified in the message itself. The <code>send</code> method
	 * calls the <code>saveChanges</code> method on the message before sending
	 * it.
	 * <p>
	 * 
	 * @param msg
	 *            the message to send
	 * @param addresses
	 *            the addresses to which to send the message
	 * @exception SendFailedException
	 *                if the message could not
	 *                be sent to some or any of the recipients.
	 * @exception MessagingException
	 * @see Message#saveChanges
	 * @see #send(Message)
	 * @see javax.mail.SendFailedException
	 */
	public static void send(Message msg, Address[] addresses) throws MessagingException {
		msg.saveChanges();
		send0(msg, addresses, null);
	}

	// send, but without the saveChanges
	private static void send0(Message msg, Address[] addresses, WriteListener listener) throws MessagingException {

		if (addresses == null || addresses.length == 0)
			throw new SendFailedException("No recipient addresses");

		/*
		 * protocols is a hashtable containing the addresses
		 * indexed by address type
		 */
		Hashtable protocols = new Hashtable();

		// Vectors of addresses
		Vector invalid = new Vector();
		Vector validSent = new Vector();
		Vector validUnsent = new Vector();

		for (int i = 0; i < addresses.length; i++) {
			// is this address type already in the hashtable?
			if (protocols.containsKey(addresses[i].getType())) {
				Vector v = (Vector) protocols.get(addresses[i].getType());
				v.addElement(addresses[i]);
			} else {
				// need to add a new protocol
				Vector w = new Vector();
				w.addElement(addresses[i]);
				protocols.put(addresses[i].getType(), w);
			}
		}

		int dsize = protocols.size();
		if (dsize == 0)
			throw new SendFailedException("No recipient addresses");

		Session s = (msg.session != null) ? msg.session : Session.getDefaultInstance(System.getProperties(), null);
		Transport transport;

		/*
		 * Optimize the case of a single protocol.
		 */
		if (dsize == 1) {
			transport = s.getTransport(addresses[0]);
			try {
				if (transport instanceof SMTPTransport) {
					((SMTPTransport) transport).setWriteListener(listener);
					if (listener != null) {
						listener.start();
					}
				}
				transport.connect();
				transport.sendMessage(msg, addresses);
			} finally {
				transport.close();
				if (transport instanceof SMTPTransport) {
					if (listener != null) {
						listener.end();
					}
					((SMTPTransport) transport).setWriteListener(null);
				}
			}
			return;
		}

		/*
		 * More than one protocol. Have to do them one at a time
		 * and collect addresses and chain exceptions.
		 */
		MessagingException chainedEx = null;
		boolean sendFailed = false;

		Enumeration e = protocols.elements();
		while (e.hasMoreElements()) {
			Vector v = (Vector) e.nextElement();
			Address[] protaddresses = new Address[v.size()];
			v.copyInto(protaddresses);

			// Get a Transport that can handle this address type.
			if ((transport = s.getTransport(protaddresses[0])) == null) {
				// Could not find an appropriate Transport ..
				// Mark these addresses invalid.
				for (int j = 0; j < protaddresses.length; j++)
					invalid.addElement(protaddresses[j]);
				continue;
			}
			try {
				if (transport instanceof SMTPTransport) {
					((SMTPTransport) transport).setWriteListener(listener);
				}
				transport.connect();
				transport.sendMessage(msg, protaddresses);
			} catch (SendFailedException sex) {
				sendFailed = true;
				// chain the exception we're catching to any previous ones
				if (chainedEx == null)
					chainedEx = sex;
				else
					chainedEx.setNextException(sex);

				// retrieve invalid addresses
				Address[] a = sex.getInvalidAddresses();
				if (a != null)
					for (int j = 0; j < a.length; j++)
						invalid.addElement(a[j]);

				// retrieve validSent addresses
				a = sex.getValidSentAddresses();
				if (a != null)
					for (int k = 0; k < a.length; k++)
						validSent.addElement(a[k]);

				// retrieve validUnsent addresses
				Address[] c = sex.getValidUnsentAddresses();
				if (c != null)
					for (int l = 0; l < c.length; l++)
						validUnsent.addElement(c[l]);
			} catch (MessagingException mex) {
				sendFailed = true;
				// chain the exception we're catching to any previous ones
				if (chainedEx == null)
					chainedEx = mex;
				else
					chainedEx.setNextException(mex);
			} finally {
				if (transport instanceof SMTPTransport) {
					((SMTPTransport) transport).setWriteListener(null);
				}
				transport.close();
			}
		}

		// done with all protocols. throw exception if something failed
		if (sendFailed || invalid.size() != 0 || validUnsent.size() != 0) {
			Address[] a = null, b = null, c = null;

			// copy address vectors into arrays
			if (validSent.size() > 0) {
				a = new Address[validSent.size()];
				validSent.copyInto(a);
			}
			if (validUnsent.size() > 0) {
				b = new Address[validUnsent.size()];
				validUnsent.copyInto(b);
			}
			if (invalid.size() > 0) {
				c = new Address[invalid.size()];
				invalid.copyInto(c);
			}
			throw new SendFailedException("Sending failed", chainedEx, a, b, c);
		}
	}

	/**
	 * Send the Message to the specified list of addresses. An appropriate
	 * TransportEvent indicating the delivery status is delivered to any
	 * TransportListener registered on this Transport. Also, if any of
	 * the addresses is invalid, a SendFailedException is thrown.
	 * Whether or not the message is still sent succesfully to
	 * any valid addresses depends on the Transport implementation.
	 * <p>
	 * 
	 * Unlike the static <code>send</code> method, the <code>sendMessage</code>
	 * method does <em>not</em> call the <code>saveChanges</code> method on the
	 * message; the caller should do so.
	 * 
	 * @param msg
	 *            The Message to be sent
	 * @param addresses
	 *            array of addresses to send this message to
	 * @see javax.mail.event.TransportEvent
	 * @exception SendFailedException
	 *                if the send failed because of
	 *                invalid addresses.
	 * @exception MessagingException
	 *                if the connection is dead or not in the
	 *                connected state
	 */
	public abstract void sendMessage(Message msg, Address[] addresses) throws MessagingException;

	// Vector of Transport listeners
	private volatile Vector transportListeners = null;

	/**
	 * Add a listener for Transport events.
	 * <p>
	 * 
	 * The default implementation provided here adds this listener to an
	 * internal list of TransportListeners.
	 * 
	 * @param l
	 *            the Listener for Transport events
	 * @see javax.mail.event.TransportEvent
	 */
	public synchronized void addTransportListener(TransportListener l) {
		if (transportListeners == null)
			transportListeners = new Vector();
		transportListeners.addElement(l);
	}

	/**
	 * Remove a listener for Transport events.
	 * <p>
	 * 
	 * The default implementation provided here removes this listener from the
	 * internal list of TransportListeners.
	 * 
	 * @param l
	 *            the listener
	 * @see #addTransportListener
	 */
	public synchronized void removeTransportListener(TransportListener l) {
		if (transportListeners != null)
			transportListeners.removeElement(l);
	}

	/**
	 * Notify all TransportListeners. Transport implementations are
	 * expected to use this method to broadcast TransportEvents.
	 * <p>
	 * 
	 * The provided default implementation queues the event into an internal
	 * event queue. An event dispatcher thread dequeues events from the queue
	 * and dispatches them to the registered TransportListeners. Note that the
	 * event dispatching occurs in a separate thread, thus avoiding potential
	 * deadlock problems.
	 */
	protected void notifyTransportListeners(int type, Address[] validSent, Address[] validUnsent, Address[] invalid, Message msg) {
		if (transportListeners == null)
			return;

		TransportEvent e = new TransportEvent(this, type, validSent, validUnsent, invalid, msg);
		queueEvent(e, transportListeners);
	}
}
